using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class BoardLocal : Board
{
    public const string MODE_CLASSIC = "Classic";
    public const string MODE_COMBO_HUNT = "Combo Hunt";

	protected Queue<Move> m_MoveChain = new Queue<Move>();
	
	public int[,] gemStand { get; protected set; }

	public const int minBoardDimension = 3;
	public float comboTimeoutScale = 1.0f;
	private XMLGems m_XMLGems;
	private bool m_IsMoveCoroutineRuning = false;
	private float m_ComboTimeout = 1.0f;
	//Board display initialization
	public override void Init(GameManager gm, int width, int height)
	{
		m_GameManager = gm;
		m_XMLGems = m_GameManager.mainXMLGems;
		m_BoardDisplay = m_GameManager.mainBoardDisplay;

		if (width < minBoardDimension || height < minBoardDimension)
			Debug.LogException(new System.Exception("Specified board dimensions are too low!"));
		gemStand = new int[width, height];

		List<List<Point>> lines;
		// Re-randomize array untill we get variation without initial matching lines, but with possibility to get one with a move
		do
		{
			RandomizeArray(gemStand);
			lines = ArrayTools2D.FindMatchingLines(gemStand);
		} while (!(lines.Count == 0 && CheckBoardForMoves()));
		Action init = new Action(Action.Type.Add, -1);
		for (int y = 0; y < gemStand.GetLength(0); y++)
		{
			for (int x = 0; x < gemStand.GetLength(1); x++)
			{
				init.from.Add(new Point(x, y));
				init.gemTypes.Add(gemStand[y, x]);
			}
		}
		Queue<Action> dummyChain = new Queue<Action>();
		dummyChain.Enqueue(init);
		InvokeOnMainActionChain(dummyChain);
	}
	// Fills board with random gem IDs obtained from XMLGems
	void RandomizeArray(int[,] array)
	{
		for (int y = 0; y < array.GetLength(0); y++)
		{
			for (int x = 0; x < array.GetLength(1); x++)
			{
                do
                {
				    array[y, x] = m_XMLGems.GetGemByID();
                } while (ArrayTools2D.FindMatchingLines(array).Count > 0);
			}

		}
	}
	// Main function that handles player input and make board correspond in manner appropriate to game mode
	public override void MakeSwap(int x1, int y1, int x2, int y2, int playerID = 0)
	{
		Move move = new Move(x1, y1, x2, y2, playerID);
        if (!IsValidMove(move))
        {
            return;
        }
		if (m_IsMoveCoroutineRuning)
		{
			m_MoveChain.Enqueue(move);
		}
		else
		{
			switch (m_GameManager.gameMode)
			{
				case MODE_CLASSIC:
					StartCoroutine(ClassicMoveMode(move));
					break;
				case MODE_COMBO_HUNT:
					StartCoroutine(ComboMoveMode(move));
					break;
				default:
					break;
			}
		}
	}
	// Combo-hunt mode
	private IEnumerator ComboMoveMode(Move move)
	{
		m_IsMoveCoroutineRuning = true;

		m_MoveChain.Enqueue(move);
		float timer = 0.0f;
		List<List<Point>> lines;
		SwitchCombo(true);
		do
		{
			timer += Time.deltaTime * comboTimeoutScale;
			if (m_MoveChain.Count != 0)
			{
				Move currentMove = m_MoveChain.Dequeue();
				if (IsPossibleMove(currentMove))
				{
					Queue<Action> newChain = new Queue<Action>();
					newChain.Enqueue(MakeMove(currentMove));
					if (m_XMLGems.isDestruct(5)) {
						lines = ArrayTools2D.FindMatchingLines (gemStand);
					} else { 
						lines = ArrayTools2D.FindMatchingLines(gemStand,6);
					}
					newChain.Enqueue(DeleteLines(lines, currentMove.playerID));
					timer = 0.0f;
					InvokeOnNewActionChain(newChain);
				}
			}
			yield return new WaitForEndOfFrame();
		} while (timer < m_ComboTimeout);
		SwitchCombo(false);
		Queue<Action> postMove = new Queue<Action>();
		do
		{
			postMove.Enqueue(Shift());
			if (m_XMLGems.isDestruct(6)) {
				lines = ArrayTools2D.FindMatchingLines (gemStand);
			} else { 
				lines = ArrayTools2D.FindMatchingLines(gemStand,6);
			}
			postMove.Enqueue(DeleteLines(lines, 0));
		} while (lines.Count != 0);
		postMove.Enqueue(Refill(true));
		InvokeOnMainActionChain(postMove);
		//while (m_BoardDisplay.isReady) yield return new WaitForEndOfFrame();
		while (!(m_BoardDisplay.isReady && m_BoardDisplay.isMainQueueEmpty))
			yield return new WaitForEndOfFrame();
		m_MoveChain.Clear();

		m_IsMoveCoroutineRuning = false;
	}
	// Classic mode
	private IEnumerator ClassicMoveMode(Move move)
	{
				Gems g = new Gems();
		m_IsMoveCoroutineRuning = true;
		var lines = ArrayTools2D.FindMatchingLines (gemStand);
		Queue<Action> moveQueue = new Queue<Action>();
		moveQueue.Enqueue(MakeMove(move));
				
		if (m_XMLGems.isDestruct(5)) {
			lines = ArrayTools2D.FindMatchingLines (gemStand);
		} else { 
			lines = ArrayTools2D.FindMatchingLines(gemStand,6);
		}
		if (lines.Count == 0)
		{
			moveQueue.Enqueue(MakeMove(move));
		}
		else
		{
			SwitchCombo(true);
			do
			{
				moveQueue.Enqueue(DeleteLines(lines, move.playerID));
				moveQueue.Enqueue(Shift());
				moveQueue.Enqueue(Refill());
				Debug.Log (m_XMLGems.isDestruct(6));
				if (m_XMLGems.isDestruct(6)) {
					lines = ArrayTools2D.FindMatchingLines (gemStand);
				} else { 
					lines = ArrayTools2D.FindMatchingLines(gemStand,6);
				}
			} while (lines.Count != 0);
		}
		InvokeOnMainActionChain(moveQueue);
		SwitchCombo(false);
        while (!(m_BoardDisplay.isReady && m_BoardDisplay.isMainQueueEmpty))
			yield return new WaitForEndOfFrame();
		m_MoveChain.Clear();

		m_IsMoveCoroutineRuning = false;
	}
	// Make move, delete matching lines, all in memory - no visualization, immediate
	private Action MakeMove(int x1, int y1, int x2, int y2, int playerID)
	{
		ArrayTools2D.Swap(gemStand, x1, y1, x2, y2);
		Action move = new Action(Action.Type.Swap, playerID);
		move.from.Add(new Point(x1, y1));
		move.to.Add(new Point(x2, y2));
		return move;
	}

	private Action MakeMove(Move move)
	{
		return MakeMove(move.x1, move.y1, move.x2, move.y2, move.playerID);
	}
	// Set every item listed in lines to zero
	private Action DeleteLines(List<List<Point>> lines, int playerID)
	{
						Action deletionAction = new Action (Action.Type.Remove, playerID);
						foreach (var line in lines) {
						if (line.Count >= 4) 
						{
								
								deletionAction.gemTypes.Add (gemStand [line [0].y, line [0].x]);
							foreach(var point in line)
							{
									gemStand [point.y, point.x] = 0;
							}
						} 
						else 
						{
								deletionAction.gemTypes.Add (gemStand [line [0].y, line [0].x]);
								foreach (var point in line) 
								{
									gemStand [point.y, point.x] = 0;
								}
						}
						}
		deletionAction.lines = lines;
			return deletionAction;	
	}
	// Notifyes BoardDisplay about item deletion
	private Action Shift()
	{
				//Debug.Log (m_XMLGems.IsShifted(6));
						Action shiftAction = new Action (Action.Type.Swap, -1);
						int zero = 0;
						for (int i = 0; i < gemStand.GetLength(0); i++) {
								for (int j = 0; j < gemStand.GetLength(1); j++) {
										if (gemStand [i, j] == 0) {
												for (int y = i - 1; y >= 0; y--) {
														if (IsValidMove (j, y + 1, j, y))
														{                
																zero = gemStand [y + 1, j];
																gemStand [y + 1, j] = gemStand [y, j];
																gemStand [y, j] = zero;
																shiftAction.from.Add (new Point (j, y));
																shiftAction.to.Add (new Point (j, y + 1));
														} else
																break;
												}
										}
								}
						}
						return shiftAction; 			
	}
	// Set every element of the board filled with zero, e.g. empty element, with random gem ID
	private Action Refill(bool noMatches = false)
	{

		Action refillAction = new Action(Action.Type.Add, -1);
		for (int y = 0; y < gemStand.GetLength(0); y++)
		{
			for (int x = 0; x < gemStand.GetLength(1); x++)
			{
				if (gemStand[y, x] == 0)
				{
                    do
                    {
					    gemStand[y, x] = m_XMLGems.AddGemByID();
                    } while (ArrayTools2D.FindMatchingLines(gemStand).Count > 0 && noMatches);
					refillAction.from.Add(new Point(x, y));
					refillAction.gemTypes.Add(gemStand[y, x]);
				}
			}
		}
		return refillAction;
	}
	// Returns true of none of the coords are out of bounds, coord are in swappable positions and at least one of the swapping item isn't null
	// Does not check whether move will result in matching lines
	private bool IsValidMove(int x1, int y1, int x2, int y2)
	{
		if (ArrayTools2D.IsOutOfBounds(gemStand, y1, x1) || ArrayTools2D.IsOutOfBounds(gemStand, y2, x2) || !ArrayTools2D.IsItemsSwappable(x1, y1, x2, y2))
		{
			return false;
		}
		else
		{
            if (gemStand[y1, x1] != 0 || gemStand[y2, x2] != 0)
            {
                return m_XMLGems.IsMovable(gemStand[y1, x1]) && m_XMLGems.IsMovable(gemStand[y2, x2]);
            }
            else
                return false;
		}
	}

	private bool IsValidMove(Move move)
	{
		return IsValidMove(move.x1, move.y1, move.x2, move.y2);
	}
	// If IsValidMove call results in success, check if this move will result in matching lines
	private bool IsPossibleMove(int x1, int y1, int x2, int y2)
	{
		if (IsValidMove(x1, y1, x2, y2))
		{
			ArrayTools2D.Swap(gemStand, x1, y1, x2, y2);
			var lines = ArrayTools2D.FindMatchingLines(gemStand);
			ArrayTools2D.Swap(gemStand, x1, y1, x2, y2);

			return lines.Count > 0;
		}
		else
		{
			return false;
		}
	}

	private bool IsPossibleMove(Move move)
	{
		return IsPossibleMove(move.x1, move.y1, move.x2, move.y2);
	}
	// Little helper functions
	private void SwitchCombo(bool begin)
	{
		Queue<Action> comboSwitch = new Queue<Action>();
		Action.Type type = begin ? Action.Type.ComboBegin : Action.Type.ComboEnd;
		comboSwitch.Enqueue(new Action(type));
		InvokeOnMainActionChain(comboSwitch);
	}

    // Checkes board for possible moves, return true if moves are possible on given board
    public bool CheckBoardForMoves()
    {
        for (int y = 0; y < gemStand.GetLength(0); y++)
        {
            for (int x = 0; x < gemStand.GetLength(1); x++)
            {
                Point position = new Point(x, y);
                List<Point> moves = new List<Point>();
                moves.Add(new Point(x - 1, y));
                moves.Add(new Point(x + 1, y));
                moves.Add(new Point(x, y - 1));
                moves.Add(new Point(x, y + 1));
                foreach (var swapPosition in moves)
                {
                    if (IsPossibleMove(position.x, position.y, swapPosition.x, swapPosition.y)) return true;
                }
            }
        }
        return false;
    }
}